﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using XRpgLibrary.ExtensionMethods;

namespace XRpgLibrary.TileEngine
{
    public class Tileset
    {
        #region Fields
        
        Texture2D image;
        Texture2D passImage;
        List<Rectangle>[] passMap;
        int tileWidthInPixels;
        int tileHeightInPixels;
        int tilesWide;
        int tilesHigh;
        Rectangle[] sourceRectangles;
        string animationScript;
        #endregion

        #region Properties

        public Texture2D Texture
        {
            get { return image; }
            private set { image = value; }
        }

        private Texture2D PassTexture
        {
            get { return passImage; }
            set { passImage = value; }
        }

        public List<Rectangle>[] PassMap
        {
            get
            {
                if (passMap == null)
                    passMap = CalculatePassMap();

                return passMap;
            }
        }

        public int TileWidth
        {
            get { return tileWidthInPixels; }
            private set { tileWidthInPixels = value; }
        }
        
        public int TileHeight
        {
            get { return tileHeightInPixels; }
            private set { tileHeightInPixels = value; }
        }
        
        public int TilesWide
        {
            get { return tilesWide; }
            private set { tilesWide = value; }
        }
        
        public int TilesHigh
        {
            get { return tilesHigh; }
            private set { tilesHigh = value; }
        }
        
        public Rectangle[] SourceRectangles
        {
            get { return (Rectangle[])sourceRectangles.Clone(); }
        }

        public string AnimationScript
        {
            get
            {
                return animationScript;
            }
            set
            {
                animationScript = value;
            }
        }

        Texture2D[] miniTiles = null;
        private Texture2D[] MiniTiles
        {
            get
            {
                if (miniTiles == null)
                    miniTiles = new Texture2D[TilesHigh * TilesWide];

                return miniTiles;
            }
            set
            {
                miniTiles = value;
            }
        }

        #endregion

        #region Constructor Region
        public Tileset(
            Texture2D image, 
            int tilesWide, 
            int tilesHigh, 
            int tileWidth, 
            int tileHeight, 
            string animationScript)
            : this (
                image, 
                null, 
                tilesWide, 
                tilesHigh, 
                tileWidth, 
                tileHeight, 
                animationScript)
        { }

        public Tileset(
            Texture2D image, 
            Texture2D passabilityImage, 
            int tilesWide, 
            int tilesHigh, 
            int tileWidth, 
            int tileHeight, 
            string animationScript)
        {
            Texture = image;
            PassTexture = passabilityImage;
            TileWidth = tileWidth;
            TileHeight = tileHeight;
            TilesWide = tilesWide;
            TilesHigh = tilesHigh;
            AnimationScript = animationScript;
            
            sourceRectangles = new Rectangle[tilesWide * tilesHigh];
            
            int tile = 0;
            for (int y = 0; y < tilesHigh; y++)
                for (int x = 0; x < tilesWide; x++)
                {
                    sourceRectangles[tile] = new Rectangle(
                    x * tileWidth,
                    y * tileHeight,
                    tileWidth,
                    tileHeight);
                    tile++;
                }
        }
        #endregion

     
        private List<Rectangle>[] CalculatePassMap()
        {
            List<Rectangle>[] map = InitializeBlankPassMap();

            //there's no passability texture, so just return a blank collection
            if (PassTexture == null)
                return map;

            //get the loop helper vars ready
            Color[] colors = new Color[TileWidth * TileHeight];
            
            //loop through each tile
            for(int y = 0; y < TilesHigh; y++)
                for (int x = 0; x < TilesWide; x++)
                {
                    //get colors for this tile
                    PassTexture.GetData<Color>(
                        0,
                        new Rectangle(
                            x * TileWidth,
                            y * TileHeight,
                            TileWidth,
                            TileHeight),
                        colors,
                        0,
                        TileWidth * TileHeight);

                    map[y * TilesWide + x] = GetTileRectangles(colors);
                }

            return map;
        }

        private List<Rectangle> GetTileRectangles(Color[] colors)
        {
            List<Rectangle> rects = new List<Rectangle>();
            int[,] grid = GetGrid(colors);

            for(int y = 0; y < TileHeight; y++)
                for(int x = 0; x < TileWidth; x++)
                    if (grid[y, x] > 0)
                    {
                        rects.Add(GetRect(grid, x, y));
                        grid = ConsumeRect(grid, rects[rects.Count - 1]);
                        y = -1;
                        x = -1;
                        break;
                    }

            return rects;
        }

        private int[,] ConsumeRect(int[,] grid, Rectangle rect)
        {
            /* Example input grid:
              
               0 0 3 2 1
               0 4 3 2 1
               3 2 1 0 0
              
             * input rect rect = (x = 2, y =  0, w = 3, h = 2)
             * 
             * Output:
               
               0 0 0 0 0
               0 4 0 0 0
               3 2 1 0 0
             
            */

            for (int y = rect.Y, cnt = rect.Y + rect.Height; y < cnt; y++)
                for (int x = rect.X, xcnt = rect.X + rect.Width; x < xcnt; x++)
                    grid[y, x] = 0;

            return CalcGrid(grid);
        }

        private Rectangle GetRect(int[,] grid, int topLeft_x, int topLeft_y)
        {
            /* Example input grid:
              
               0 0 3 2 1
               0 4 3 2 1
               3 2 1 0 0
              
             * if top left x,y = 2,0
             * then grid[y,x] = 3 (from above example grid)
             * 
             * the return will be a rect = x = 2, y =  0, w = 3, h= 2
             * 
            */

            Rectangle rect = new Rectangle();
            rect.X = topLeft_x;
            rect.Y = topLeft_y;
            rect.Width = grid[topLeft_y, topLeft_x]; //the value of the grid element is always the width of the rect
            rect.Height = 1;

            //go down one row at a time looking for elements with the same width
            //when you find one that's not the same, break
            for (int y = rect.Y + 1; y < TileHeight; y++)
                if (grid[y, rect.X] == grid[rect.Y, rect.X])
                    rect.Height++;
                else
                    break;

            return rect;
        }

        private int[,] CalcGrid(int[,] grid)
        {
            //set each element that isn't zero to the number of non-zero tiles adjacent
            for (int y = 0; y < TileHeight; y++)
                for (int x = 0; x < TileWidth; x++)
                    if (grid[y, x] > 0)
                    {
                        grid[y, x] = 1;
                        for (int w = x + 1; w < TileWidth; w++)
                            if (grid[y, w] > 0)
                                grid[y, x]++;
                            else
                                break;
                    }

            /* Example:
              
              0 0 3 2 1
              0 4 3 2 1
              3 2 1 0 0
              
           */

            return grid;
        }

        private int[,] GetGrid(Color[] colors)
        {
            int [,] grid = new int [TileHeight, TileWidth];

            //set the grid elements to either 0 (black) or 1 (white)
            for (int y = 0; y < TileHeight; y++)
                for (int x = 0; x < TileWidth; x++)
                    grid[y, x] = colors[y * TileHeight + x] == Color.White ? 1 : 0;

            /* Example:
              
               0 0 1 1 1
               0 1 1 1 1
               1 1 1 0 0
              
            */
            
            return CalcGrid(grid);
        }



        private List<Rectangle>[] InitializeBlankPassMap()
        {
            List<Rectangle>[] map = new List<Rectangle>[TilesWide * TilesHigh];

            for (int i = 0, cnt = map.Length; i < cnt; i++)
                map[i] = new List<Rectangle>();

            return map;
        }

        private string DebugPrintGrid(int[,] grid)
        {
            StringBuilder sb = new StringBuilder();

            for (int y = 0; y < TileHeight; y++)
            {
                for (int x = 0; x < TileWidth; x++)
                    sb.Append(grid[y, x] + " ");
                sb.Append("\n");
            }

            return sb.ToString();
        }

        public Texture2D GetAverage(int tileIndex, GraphicsDevice graphicsDevice)
        {
            if (MiniTiles[tileIndex] == null)
                MiniTiles[tileIndex] = GenerateAverage(tileIndex, graphicsDevice);

            return MiniTiles[tileIndex];
        }

        private Texture2D GenerateAverage(int tileIndex, GraphicsDevice graphicsDevice)
        {
            Vector4 colorData = new Vector4(0, 0, 0, 0);
            Color[] colors = new Color[TileWidth * TileHeight];
            Texture.GetData<Color>(0, sourceRectangles[tileIndex], colors, 0, TileWidth * TileHeight);

            for(int y = 0, cnt = TileHeight; y < cnt; y++)
                for(int x = 0, xcnt = TileWidth; x < xcnt; x++)
                {
                    colorData.X += colors[y * TileHeight + x].R;
                    colorData.Y += colors[y * TileHeight + x].G;
                    colorData.Z += colors[y * TileHeight + x].B;
                    colorData.W += colors[y * TileHeight + x].A;
                }

            Color avgColor = new Color(
                (int)(colorData.X / colors.Length),
                (int)(colorData.Y / colors.Length), 
                (int)(colorData.Z / colors.Length), 
                (int)(colorData.W / colors.Length));

            Texture2D avgTexture = new Texture2D(graphicsDevice, 1, 1, false, SurfaceFormat.Color);
            avgTexture.SetData<Color>(new Color[] { avgColor });

            return avgTexture;
        }

        public List<Rectangle> GetPassRects(int tileIndex)
        {
            List<Rectangle> ret = new List<Rectangle>();

            foreach (Rectangle passRect in PassMap[tileIndex])
                ret.Add(passRect.Clone());

            return ret;
        }
    }
}
